//
// Created by Gao Shihao on 2023/8/31.
//

#ifndef PLC2LLVM_DATETYPE_H
#define PLC2LLVM_DATETYPE_H

#include <plc2llvm/TypeSystem/BasicType.h>

namespace plcst {

    class Year {
    public:
        explicit Year(int y): year(y) {};

    private:
        i32 year;
    };

    class Month {
    public:
        explicit Month(int m): month(m) {};

    private:
        i32 month;
    };

    class Day {
    public:
        explicit Day(int d): day(d) {};

    private:
        i32 day;
    };

    class Date {
    public:
        Date(Year y, Month m, Day d): year(y), month(m), day(d) {}

    private:
        Month month;
        Day day;
        Year year;
    };

    // 这里用三个int记录日期。
    // 实际上plc要求用64位表示，这里留一个转换的接口。
    template <TypeKind Kind, int size>
    class DateType: public BasicType {
    public:
        explicit DateType(std::string name, Date d=Date(Year(1970),Month(1),Day(1))):
                BasicType(std::move(name)), date(d) {}

        explicit DateType(DateType<Kind, size>* another) : BasicType(another), date(Date(Year(1970),Month(1),Day(1))) {}

        [[nodiscard]] i64 convert_to_64bits() const;

        [[nodiscard]] Date const& getInitValue() const;

        [[nodiscard]] TypeKind getTypeKind() const override;

        [[nodiscard]] int getNBits() const override;

        virtual TypeMsg typeSynthesisForBinaryOperator(ExpressionOperator op, std::shared_ptr<Type> another) override{
            return {nullptr, 2}; 
        }

        virtual TypeMsg typeSynthesisForUnaryOperator(ExpressionOperator op) override {
            return {nullptr, 2};
        }

    private:
        Date date;
        static inline int nbits = size;
        static inline TypeKind kind = Kind;
        
    public:
        virtual TypeMsg getLargerType(std::shared_ptr<Type> another) override {
            auto thisShared = ScopeManager::getScopeManager().getGlobalScope()->find<Type>(this->getTypeName());

            auto anotherTypeKind = another->getTypeKind();
            if(this->getTypeKind() == anotherTypeKind){ // this = another
                return {thisShared, 0};
            }

            auto typeMachine = TypeMachine::getTypeMachine();
            if(!typeMachine.isDateType(anotherTypeKind)){ // another is not date type
                return {nullptr, 2};
            }
            auto thisNBit = this->getNBits();
            auto anotherDetail = std::dynamic_pointer_cast<BasicType>(another);
            auto anotherNBit = anotherDetail->getNBits();
            if(thisNBit > anotherNBit){// return larger one
                return {thisShared, 0};
            }else{
                return {another, 0};
            }
        }

        virtual llvm::Value* castTo(std::shared_ptr<Type> destTy, llvm::Value* src, llvm::IRBuilder<>* builder) override {
            auto anotherTypeKind = destTy->getTypeKind();
            if(this->getTypeKind() == anotherTypeKind){
                return src;
            }
            TypeMachine& typeMachine = TypeMachine::getTypeMachine();
            if(typeMachine.isDateType(anotherTypeKind)){
                auto thisNBit = this->getNBits();
                auto anotherDetail = std::dynamic_pointer_cast<BasicType>(destTy);
                auto anotherNBit = anotherDetail->getNBits();
                if(thisNBit > anotherNBit){// return larger one
                    return builder->CreateTrunc(src, destTy->llvmty);
                }else{
                    return builder->CreateSExt(src, destTy->llvmty);
                }
            }else{
                throw SemanticError("bad date time cast");
                return nullptr;
            }
        }

        virtual std::shared_ptr<Type> clone() override {
            return std::make_shared<DateType<Kind, size>>(this);
        }
    };

    template<TypeKind Kind, int size>
    TypeKind DateType<Kind, size>::getTypeKind() const {
        return TypeKind::DATE;
    }

    template<TypeKind Kind, int size>
    i64 DateType<Kind, size>::convert_to_64bits() const {
        return 0;
    }

    template<TypeKind Kind, int size>
    Date const &DateType<Kind, size>::getInitValue() const {
        return date;
    }

    template<TypeKind Kind, int size>
    int DateType<Kind, size>::getNBits() const {
        return DateType<Kind, size>::nbits;
    }

}

#endif //PLC2LLVM_DATETYPE_H
